#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <fcntl.h>
#include <unistd.h>

#ifdef USE_CAPSICUM
#include <sys/capsicum.h>
#endif

#include <err.h>
#include <sysexits.h>

#include "au.h"
#include "common.h"

#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef BSIZE
#define BSIZE 8000
#endif

#ifdef SIGINFO
static size_t info_in = 0;
static size_t info_out = 0;
#endif

#ifdef SIGINFO
static void	siginfo(int);
#endif
static ssize_t	auresample_simple(struct au *, uint32_t, void *,
		    size_t, void *);
static ssize_t	ncpy(int, int, void *, size_t);

int
main(int argc, char *argv[])
{
#ifdef USE_CAPSICUM
	cap_rights_t rights;
#endif
	struct au au = AU_INITIALIZER;
	void *b;
	char *p;
	double rmod = 0;
	ssize_t r;
	size_t bsize = BSIZE;
	size_t isize;
	size_t osize;
	size_t ssize;
	size_t i;
	uint32_t rate = 0;
	int setrate = 0;
	int nflag = 0;
	int c;

	while ((c = getopt(argc, argv, "nm:r:")) != -1)
		switch (c) {
		case 'n':
			nflag = 1;
			break;
		case 'm':
			errno = 0;
			rmod = strtod(optarg, &p);
			if (!(rmod > 0))
				errno = ERANGE;
			if (errno != 0 || p == optarg || *p != '\0')
				err(EX_USAGE, "Invalid number %s",
				    optarg);
			setrate = 1;
			break;
		case 'r':
			if ((rate = strtou32(optarg)) == 0)
				err(EX_USAGE,
				    "The target rate must not be zero");
			setrate = 1;
			break;
		default:
			goto usage;
		}
	if (optind != argc)
		goto usage;
#ifdef SIGINFO
	(void)signal(SIGINFO, &siginfo);
#endif
	if ((b = malloc(bsize)) == NULL)
		err(EX_OSERR, "Could not allocate the buffer");
#ifdef USE_PLEDGE
	(void)pledge("stdio", NULL);
#endif
#ifdef USE_CAPSICUM
	(void)cap_rights_init(&rights, CAP_READ);
	if (cap_rights_limit(STDIN_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "Could not limit the standard input");
	(void)cap_rights_init(&rights, CAP_WRITE);
	if (cap_rights_limit(STDOUT_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "Could not limit the standard output");
	if (cap_rights_limit(STDERR_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "Could not limit the standard error");
#endif
	while (nread(STDIN_FILENO, b, AU_SIZE) == AU_SIZE) {
		if (au_gethdr(&au, b) != 0)
			errx(EX_DATAERR, "Bad header");
		if (rmod > 0)
			rate = (double)au.au_rate * rmod;
		ssize = au_ssize(&au);
		isize = (size_t)au.au_rate * ssize;
		osize = (size_t)rate * ssize;
		if (isize > bsize || osize > bsize) {
			bsize = MAX(isize, osize);
			if ((b = realloc(b, bsize)) == NULL)
				err(EX_OSERR,
				    "Could not realloc buffer");
		}
		if (!setrate) {
			if ((rate = au.au_rate) == 0)
				err(EX_DATAERR,
				    "The target rate must not be zero");
			setrate = 1;
		}
		if (!nflag)
			put4((char *)b + 16, rate);
		if (nwrite(STDOUT_FILENO, b, AU_SIZE) < 0)
			err(EX_IOERR,
			    "Could not write audio Header");
		for (i = AU_SIZE; i < au.au_off; i += r)
			if ((r = ncpy(STDIN_FILENO, STDOUT_FILENO, b,
			    MIN(au.au_off - i, isize))) < 0)
				err(EX_IOERR, "error while copying "
				    "stdin -> stdout");
		while ((r = nread(STDIN_FILENO,
		    (char *)b + (bsize - isize),
		    MIN(isize, au.au_size))) > 0) {
#ifdef SIGINFO
			info_in += r / ssize;
#endif
			i = r;
			if (isize != osize &&
			    (r = auresample_simple(&au, rate,
			    (char *)b + (bsize - isize), r, b)) < 0)
				err(EX_SOFTWARE, "auresample_simple");
			if ((r = nwrite(STDOUT_FILENO, b, r)) < 0)
				err(EX_IOERR, "Write error");
#ifdef SIGINFO
			info_out += r / ssize;
#endif
			if (au.au_size != UINT32_MAX &&
			    (au.au_size -= i) == 0)
				break;
		}
		if (r == 0)
			break;
	}
	free(b);
	return (0);
usage:
	(void)fprintf(stderr,
	    "usage: auresample [-n] [-r rate] [-m modifier]\n");
	return (EX_USAGE);
}



#ifdef SIGINFO
void
siginfo(int sig)
{
	/* auresample: XXXXXX samples in XXXXXX samples out */
	char b[12 + 6 + 12 + 6 + 13];
	size_t i = 0;

	(void)memcpy((char *)b + i, "auresample: ", 12);
	i += 12;
	i += szwrite(b + i, info_in, NULL, NULL);
	(void)memcpy(b + i, " samples in ", 12);
	i += 12;
	i += szwrite(b + i, info_out, NULL, NULL);
	(void)memcpy(b + i, " samples out\n ", 13);
	i += 13;
	(void)write(STDERR_FILENO, b, i);
}
#endif


ssize_t
auresample_simple(struct au *au, uint32_t rate, void *b, size_t s,
    void *o)
{
	size_t ssize;
	size_t i;
	size_t j;
	uint64_t x;

	assert(au != NULL);
	for (i = 0, j = 0, x = 0, ssize = au_ssize(au); i + ssize <= s;
	    i += ssize)
		for (x += rate; x >= au->au_rate;
		    j += ssize, x -= au->au_rate)
			(void)memcpy((char *)o + j, (char *)b + i,
			    ssize);
	return (j);
}

ssize_t
ncpy(int in, int out, void *b, size_t s)
{
	ssize_t t;

	if ((t = nread(in, b, s)) > 0)
		t = nwrite(out, b, s);
	return (t);
}
